//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <boost/mysql/connection.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/rows_view.hpp>

#include "integration_test_common.hpp"
#include "metadata_validator.hpp"
#include "test_common.hpp"

using namespace boost::mysql::test;
using boost::mysql::common_server_errc;
using boost::mysql::execution_state;
using boost::mysql::field_view;
using boost::mysql::results;
using boost::mysql::row_view;

namespace {

BOOST_AUTO_TEST_SUITE(test_spotchecks)

auto err_net_samples = create_network_samples({
    "tcp_sync_errc",
    "tcp_sync_exc",
    "tcp_async_callback",
    "tcp_async_coroutines",
});

// Handshake
BOOST_MYSQL_NETWORK_TEST(handshake_success, network_fixture, all_network_samples())
{
    setup_and_physical_connect(sample.net);
    conn->handshake(params).validate_no_error();
    BOOST_TEST(conn->uses_ssl() == var->supports_ssl());
}

BOOST_MYSQL_NETWORK_TEST(handshake_error, network_fixture, err_net_samples)
{
    setup_and_physical_connect(sample.net);
    params.set_database("bad_database");
    conn->handshake(params).validate_error(
        common_server_errc::er_dbaccess_denied_error,
        {"database", "bad_database"}
    );
}

// Connect: success is already widely tested throughout integ tests
BOOST_MYSQL_NETWORK_TEST(connect_error, network_fixture, err_net_samples)
{
    setup(sample.net);
    set_credentials("integ_user", "bad_password");
    conn->connect(params).validate_error(
        common_server_errc::er_access_denied_error,
        {"access denied", "integ_user"}
    );
    BOOST_TEST(!conn->is_open());
}

// Start query
BOOST_MYSQL_NETWORK_TEST(start_query_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);

    execution_state st;
    conn->start_query("SELECT * FROM empty_table", st).get();
    BOOST_TEST(!st.complete());
    validate_2fields_meta(st.meta(), "empty_table");
}

BOOST_MYSQL_NETWORK_TEST(start_query_error, network_fixture, err_net_samples)
{
    setup_and_connect(sample.net);

    execution_state st;
    conn->start_query("SELECT field_varchar, field_bad FROM one_row_table", st)
        .validate_error(common_server_errc::er_bad_field_error, {"unknown column", "field_bad"});
}

// Query
BOOST_MYSQL_NETWORK_TEST(query_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);

    results result;
    conn->query("SELECT 'hello', 42", result).get();
    BOOST_TEST(result.rows().size() == 1u);
    BOOST_TEST(result.rows()[0] == makerow("hello", 42));
    BOOST_TEST(result.meta().size() == 2u);
}

BOOST_MYSQL_NETWORK_TEST(query_error, network_fixture, err_net_samples)
{
    setup_and_connect(sample.net);

    results result;
    conn->query("SELECT field_varchar, field_bad FROM one_row_table", result)
        .validate_error(common_server_errc::er_bad_field_error, {"unknown column", "field_bad"});
}

// Prepare statement
BOOST_MYSQL_NETWORK_TEST(prepare_statement_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);
    auto stmt = conn->prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)").get();
    BOOST_TEST_REQUIRE(stmt.valid());
    BOOST_TEST(stmt.id() > 0u);
    BOOST_TEST(stmt.num_params() == 2u);
}

BOOST_MYSQL_NETWORK_TEST(prepare_statement_error, network_fixture, err_net_samples)
{
    setup_and_connect(sample.net);
    conn->prepare_statement("SELECT * FROM bad_table WHERE id IN (?, ?)")
        .validate_error(common_server_errc::er_no_such_table, {"table", "doesn't exist", "bad_table"});
}

// Start statement execution (iterator version)
BOOST_MYSQL_NETWORK_TEST(start_statement_execution_it_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);

    // Prepare
    auto stmt = conn->prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)").get();

    // Execute
    execution_state st;
    std::forward_list<field_view> params{field_view("item"), field_view(42)};
    conn->start_statement_execution(stmt, params.begin(), params.end(), st).validate_no_error();
    validate_2fields_meta(st.meta(), "empty_table");
    BOOST_TEST(!st.complete());
}

BOOST_MYSQL_NETWORK_TEST(start_statement_execution_it_error, network_fixture, err_net_samples)
{
    setup_and_connect(sample.net);
    start_transaction();

    // Prepare
    auto stmt = conn->prepare_statement("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)")
                    .get();

    // Execute
    execution_state st;
    std::forward_list<field_view> params{field_view("f0"), field_view("bad_date")};
    conn->start_statement_execution(stmt, params.begin(), params.end(), st)
        .validate_error(
            common_server_errc::er_truncated_wrong_value,
            {"field_date", "bad_date", "incorrect date value"}
        );
}

// Start statement execution (tuple version)
BOOST_MYSQL_NETWORK_TEST(start_statement_execution_tuple_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);

    // Prepare
    auto stmt = conn->prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)").get();

    // Execute
    execution_state st;
    conn->start_statement_execution(stmt, field_view(42), field_view(40), st).validate_no_error();
    validate_2fields_meta(st.meta(), "empty_table");
    BOOST_TEST(!st.complete());
}

BOOST_MYSQL_NETWORK_TEST(start_statement_execution_tuple_error, network_fixture, err_net_samples)
{
    setup_and_connect(sample.net);
    start_transaction();

    // Prepare
    auto stmt = conn->prepare_statement("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)")
                    .get();

    // Execute
    execution_state st;
    conn->start_statement_execution(stmt, field_view("abc"), field_view("bad_date"), st)
        .validate_error(
            common_server_errc::er_truncated_wrong_value,
            {"field_date", "bad_date", "incorrect date value"}
        );
}

// Execute statement
BOOST_MYSQL_NETWORK_TEST(execute_statement_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);

    // Prepare
    auto stmt = conn->prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)").get();

    // Execute
    results result;
    conn->execute_statement(stmt, field_view("item"), field_view(42), result).validate_no_error();
    BOOST_TEST(result.rows().size() == 0u);
}

BOOST_MYSQL_NETWORK_TEST(execute_statement_error, network_fixture, err_net_samples)
{
    setup_and_connect(sample.net);
    start_transaction();

    // Prepare
    auto stmt = conn->prepare_statement("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)")
                    .get();

    // Execute
    results result;
    conn->execute_statement(stmt, field_view("f0"), field_view("bad_date"), result)
        .validate_error(
            common_server_errc::er_truncated_wrong_value,
            {"field_date", "bad_date", "incorrect date value"}
        );
}

// Close statement: no server error spotcheck
BOOST_MYSQL_NETWORK_TEST(close_statement_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);

    // Prepare a statement
    auto stmt = conn->prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)").get();

    // Close the statement
    conn->close_statement(stmt).validate_no_error();

    // The statement is no longer valid
    results result;
    conn->execute_statement(stmt, field_view("a"), field_view("b"), result).validate_any_error();
}

// Read some rows: no server error spotcheck
BOOST_MYSQL_NETWORK_TEST(read_some_rows_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);

    // Generate an execution state
    execution_state st;
    conn->start_query("SELECT * FROM one_row_table", st);
    BOOST_TEST_REQUIRE(!st.complete());

    // Read once. st may or may not be complete, depending
    // on how the buffer reallocated memory
    auto rows = conn->read_some_rows(st).get();
    BOOST_TEST((rows == makerows(2, 1, "f0")));

    // Reading again should complete st
    rows = conn->read_some_rows(st).get();
    BOOST_TEST(rows.empty());
    validate_eof(st);

    // Reading again does nothing
    rows = conn->read_some_rows(st).get();
    BOOST_TEST(rows.empty());
    validate_eof(st);
}

// Ping
BOOST_MYSQL_NETWORK_TEST(ping_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);
    conn->ping().validate_no_error();
}

BOOST_MYSQL_NETWORK_TEST(ping_error, network_fixture, all_network_samples())
{
    setup(sample.net);

    // Ping should return an error for an unconnected connection
    conn->ping().validate_any_error();
}

// Quit connection: no server error spotcheck
BOOST_MYSQL_NETWORK_TEST(quit_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);

    // Quit
    conn->quit().validate_no_error();

    // We are no longer able to query
    results result;
    conn->query("SELECT 1", result).validate_any_error();
}

// Close connection: no server error spotcheck
BOOST_MYSQL_NETWORK_TEST(close_connection_success, network_fixture, all_network_samples())
{
    setup_and_connect(sample.net);

    // Close
    conn->close().validate_no_error();

    // We are no longer able to query
    boost::mysql::results result;
    conn->query("SELECT 1", result).validate_any_error();

    // The stream is closed
    BOOST_TEST(!conn->is_open());

    // Closing again returns OK (and does nothing)
    conn->close().validate_no_error();

    // Stream (socket) still closed
    BOOST_TEST(!conn->is_open());
}

// TODO: move this to a unit test
BOOST_MYSQL_NETWORK_TEST(not_open_connection, network_fixture, err_net_samples)
{
    setup(sample.net);
    conn->close().validate_no_error();
    BOOST_TEST(!conn->is_open());
}

BOOST_AUTO_TEST_SUITE_END()  // test_spotchecks

}  // namespace
